unit ImportPricesGui;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,

  GlobalRecordsAndEnums, Items, InventionCalc, ImportProgressGui, EVECentral, Region,
  EveCentralRetriever,

  XMLDoc, XMLIntf,
  ExtCtrls;

type
  TfrmImportPrices = class(TForm)
    lblImport: TLabel;
    btnImport: TButton;
    btnCancel: TButton;
    lblAmount: TLabel;
    btnHelp: TButton;
    Label3: TLabel;
    rgSetting: TRadioGroup;
    gbRegions: TGroupBox;
    btnRegionsAll: TButton;
    btnRegionsEmpire: TButton;
    btnRegionsZero: TButton;
    btnRegionsNone: TButton;
    procedure btnImportClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure btnHelpClick(Sender: TObject);
    procedure btnRegionsAllClick(Sender: TObject);
    procedure btnRegionsEmpireClick(Sender: TObject);
    procedure btnRegionsZeroClick(Sender: TObject);
    procedure btnRegionsNoneClick(Sender: TObject);
  private
    inventioncalc: TInventionCalc;
    items: TNewItemsList;
//    function RetrievePrice(itemID: Integer; setting: String): SReturnValue;
  public
    constructor Create(AOwner: TComponent; itemslist: TNewItemsList; invCalc: TInventionCalc); reintroduce;
  end;

var
  frmImportPrices: TfrmImportPrices;

implementation

uses
  StrUtils, ExtActns, Math;

{$R *.dfm}

constructor TfrmImportPrices.Create(AOwner: TComponent; itemslist: TNewItemsList; invCalc: TInventionCalc);
var
  i: Integer;
  region: TRegion;
  checkbox: TCheckBox;
  top, left: Integer;
  regions0, regions1, regions2: Double;
begin
  inherited Create(AOwner);
  inventionCalc := invCalc;
  items := itemslist;

  rgSetting.ItemIndex := inventioncalc.UserSettings.Settings.ImportPricesDefaultSettingIndex;
  if (Length(items) = 1) then lblAmount.Caption := 'You have selected ' + IntToStr(Length(items)) + ' item.'
  else lblAmount.Caption := 'You have selected ' + IntToStr(Length(items)) + ' items.';

  top := 16;
  left := 8;
  for i := 0 to inventionCalc.Regions.Count -1 do begin
    region := inventionCalc.Regions.GetRegion(i);
    checkbox := TCheckBox.Create(gbRegions);
    checkbox.Top := top;
    checkbox.Left := left;
    checkbox.Width := 117;
    checkbox.Parent := gbRegions;
    checkbox.Caption := region.Name;
    checkbox.Name := 'chkRegion_' + IntToStr(region.ID);
    top := top + 16;
    if (top > 280) then begin
      top := 16;
      left := left + 125;
    end;
  end;

  // load checkbox checked status
  regions0 := inventionCalc.UserSettings.Settings.ImportPricesRegions0;
  regions1 := inventionCalc.UserSettings.Settings.ImportPricesRegions1;
  regions2 := inventionCalc.UserSettings.Settings.ImportPricesRegions2;

  for i := 29 downto 0 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (regions0 - Math.Power(2, i) >= 0) then begin
      checkbox.Checked := true;
      regions0 := regions0 - Math.Power(2, i);
    end;
  end;
  for i := 59 downto 30 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (regions1 - Math.Power(2, i-30) >= 0) then begin
      checkbox.Checked := true;
      regions1 := regions1 - Math.Power(2, (i-30));
    end;
  end;
  for i := gbRegions.ControlCount -1 downto 60 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (regions2 - Math.Power(2, i-60) >= 0) then begin
      checkbox.Checked := true;
      regions2 := regions2 - Math.Power(2, (i-60));
    end;
  end;
end;

procedure TfrmImportPrices.btnHelpClick(Sender: TObject);
var
  run: TFileRun;
begin
  run := TFileRun.Create(self);
  run.FileName := 'help\ImportPrices.html';
  run.Execute;
  run.Free;
end;

procedure TfrmImportPrices.btnImportClick(Sender: TObject);
var
  i, j, len: Integer;
  setting, tmpsetting: String;
  gui: TfrmImportProgress;
  returnvalue: SReturnValue;
  cached, retrieved: Integer;
  found: Boolean;
  message: String;
  cachedstring, retrievedstring: String;
  itemstoretrieve: array of Integer;
  checkbox: TCheckBox;
  region: TRegion;
  canretrieve: Boolean;
  regions0, regions1, regions2: Double;
  parameterlists: TList;
  smallitemslist, itemslist, regionslist, textresult: TStringList;
  EveCentralRetrieverThread: TEveCentralRetriever;
  EveCentralRetrieverThreadIsDone: Boolean;
  XMLresultstring, pricestring: String;
  retrieve, canprocessresult: Boolean;
  XMLDoc, tempXMLDoc, XMLresult: TXMLDocument;
  tempitemsnode, itemsnode, root, node, resultroot, resultnode: IXMLNode;
  cachedtime, nowtime: TDateTime;
  price: Double;
begin
  cached := 0;
  retrieved := 0;

  // disable the "Import" button so the user cannot spam it
  btnImport.Enabled := false;

  // save the selected regions
  regions0 := 0;
  regions1 := 0;
  regions2 := 0;
  for i := 0 to 29 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (checkbox.Checked) then regions0 := regions0 + Math.Power(2, i);
  end;
  for i := 30 to 59 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (checkbox.Checked) then regions1 := regions1 + Math.Power(2, (i-30));
  end;
  for i := 60 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (checkbox.Checked) then regions2 := regions2 + Math.Power(2, (i-60));
  end;

  inventionCalc.UserSettings.Settings.ImportPricesRegions0 := regions0;
  inventionCalc.UserSettings.Settings.ImportPricesRegions1 := regions1;
  inventionCalc.UserSettings.Settings.ImportPricesRegions2 := regions2;

  case rgSetting.ItemIndex of
    0: setting := 'sell\min';
    1: setting := 'sell\avg';
    2: setting := 'sell\max';
    3: setting := 'sell\median';
    4: setting := 'buy\min';
    5: setting := 'buy\avg';
    6: setting := 'buy\max';
    7: setting := 'buy\median';
    8: setting := 'all\min';
    9: setting := 'all\avg';
    10: setting := 'all\max';
    11: setting := 'all\median';
  end;

  // Create the necessary directory and file, if they don't exist
  if (not DirectoryExists('res\EVE-Central')) then CreateDir('res\EVE-Central');
  if (not FileExists('res\EVE-Central\AllItems.xml')) then begin
    XMLDoc := TXMLDocument.Create(self);
    XMLDoc.Active := true;
    XMLDoc.AddChild('evec_api');
    XMLDoc.DocumentElement.Attributes['version'] := '2.0';
    XMLDoc.DocumentElement.Attributes['method'] := 'marketstat_xml';
    XMLDoc.DocumentElement.AddChild('marketstat');
    XMLDoc.SaveToFile('res\EVE-Central\AllItems.xml');
    XMLDoc.Free;
  end;

  gui := TfrmImportProgress.Create(self, 'selected items checked', Length(items));
  gui.Show;

  // read items from the file, to determine if they should be retrieved from EVE-Central or should be loaded from cache
  XMLDoc := TXMLDocument.Create(self);
  XMLDoc.LoadFromFile('res\EVE-Central\AllItems.xml');
  itemslist := TStringList.Create;
  canretrieve := false;
  // check each item that the user selected on the edit - items screen
  for i := 0 to Length(items) -1 do begin
    root := XMLDoc.DocumentElement.ChildNodes[0]; // <marketstat> element
    found := false;
    // search the entire XML document for this specific item
    for j := 0 to root.ChildNodes.Count -1 do begin
      node := root.ChildNodes[j];
      if (node.Attributes['id'] = items[i].ID) then begin
        found := true;
        // item found, check when its price was retrieved
        if (TryStrToDateTime(node.ChildNodes['retrievedat'].Text, cachedtime)) then begin
          nowtime := Now;
          if (nowtime - cachedtime >= 1) then begin // only allow retrieval once per 24 hours
            itemslist.Add('typeid=' + IntToStr(items[i].ID));
            canretrieve := true;
          end
          else begin
            // item price was retrieved less than 24 hours ago. Read it again from the XML file.
            cached := cached + 1;
            tmpsetting := setting;
            while (AnsiContainsStr(tmpsetting, '\')) do begin
              node := node.ChildNodes[LeftStr(tmpsetting, Pos('\', tmpsetting) - 1)];
              tmpsetting := RightStr(tmpsetting, Length(tmpsetting) - Pos('\', tmpsetting));
            end;
            pricestring := node.ChildNodes[tmpsetting].Text;
            pricestring := StringReplace(pricestring, '.', global_ds, [rfReplaceAll]);
            if (TryStrToFloat(pricestring, price)) then items[i].Price := price
//            else result.Error := 'Error retrieving price for item' + items[i].Name;
          end;
        end;
      end;
    end;
    if (not found) then begin
      // item was not found in the XML file
      itemslist.Add('typeid=' + IntToStr(items[i].ID));
      canretrieve := true;
    end;
    gui.Advance;
    gui.BringToFront;
    application.ProcessMessages;
    if (gui.AbortedByUser) then exit;
  end;

  // check region selection
  regionslist := TStringList.Create;
  gui.Change('regions checked for selection', gbRegions.ControlCount);
  application.ProcessMessages;
  if (gui.AbortedByUser) then exit;
  for i := 0 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    if (checkbox.Checked) then begin
      region := inventionCalc.Regions.GetRegionByID(StrToInt(RightStr(checkbox.Name, Length(checkbox.Name) - POS('_', checkbox.Name))));
      regionslist.Add('regionlimit=' + IntToStr(region.ID));
    end;
    gui.Advance;
    gui.BringToFront;
    application.ProcessMessages;
    if (gui.AbortedByUser) then exit;
  end;

  canprocessresult := true;
  if (canretrieve) then begin
    application.ProcessMessages;
    if (gui.AbortedByUser) then exit;
    // split up items list in smaller lists of 20 items each, as EVE-Central only allows 20 items to be retrieved at once
    parameterlists := TList.Create;
    smallitemslist := TStringList.Create;
    for i := 0 to itemslist.Count -1 do begin
      if (smallitemslist.Count = 20) then begin
        // list is full. Add region information and create a new list
        for j := 0 to regionslist.Count -1 do begin
          smallitemslist.Add(regionslist[j]);
        end;
        parameterlists.Add(smallitemslist);
        smallitemslist := TStringList.Create;
      end;
      smallitemslist.Add(itemslist[i]);
    end;
    // Also add the last few items after adding region information
    for j := 0 to regionslist.Count -1 do begin
      smallitemslist.Add(regionslist[j]);
    end;
    parameterlists.Add(smallitemslist);

    gui.BringToFront;
    gui.Change('item lists sent to EVE-Central', parameterlists.Count);

    // create EVE-Central retriever thread
    EveCentralRetrieverThread := TEveCentralRetriever.Create(true);
    EveCentralRetrieverThread.FreeOnTerminate := false;
    EveCentralRetrieverThread.ParameterLists := parameterlists;
    EveCentralRetrieverThread.Version := FloatToStr(global_version);

    // send request to EVE-Central (by activating the thread)
    EveCentralRetrieverThread.Resume;

    // wait for the thread to finish
    while (not EveCentralRetrieverThread.Done) do begin
      Application.ProcessMessages;
      if (gui.AbortedByUser) then begin
        EveCentralRetrieverThread.Terminate;
        exit;
      end;
      gui.SetCounter(EveCentralRetrieverThread.ReceivedLists);
      gui.BringToFront;
      sleep(100);
    end;

    // Thread done, check for error and parse result
    if (EveCentralRetrieverThread.Error <> '') then begin
      canprocessresult := false;
      MessageDlg(EveCentralRetrieverThread.Error, mtError, [mbOk], 0);
    end;

    textresult := EveCentralRetrieverThread.TextResult;

    // do some cleanup now that we have the result
    itemslist.Free;
    regionslist.Free;
    for i := 0 to parameterlists.Count -1 do begin
      TList(parameterlists[i]).Free;
    end;
    parameterlists.Free;
    EveCentralRetrieverThread.Terminate;

{    canprocessresult := true;
      XMLresultstring := '<?xml version="1.0" encoding="UTF-8"?><!-- Automatically generated data from EVE-Central.com --><!-- This is the new API :-) --><evec_api version="2.0" method="marketstat_xml"><marketstat>' +
      '<type id="34">' +
      '<all><volume>17608149540</volume><avg>20</avg><max>30.0</max><min>0.5</min><stddev>1.51402759799</stddev><median>3.17</median></all>' +
      '<buy><volume>8808528255</volume><avg>10</avg><max>3.3</max><min>0.5</min><stddev>0.562613117485</stddev><median>2.5099</median></buy>' +
      '<sell><volume>8799621285</volume><avg>30</avg><max>30.0</max><min>2.5</min><stddev>1.60552316236</stddev><median>3.2</median></sell>' +
      '</type><type id="35">' +
      '<all><volume>7767888943</volume><avg>7.40870858896</avg><max>450.0</max><min>1.0</min><stddev>35.0507949117</stddev><median>4.22</median></all>' +
      '<buy><volume>5927667457</volume><avg>4.11598215052</avg><max>4.55</max><min>1.0</min><stddev>0.738000375842</stddev><median>3.97</median></buy>' +
      '<sell><volume>1840221486</volume><avg>6.26024200795</avg><max>450.0</max><min>3.95</min><stddev>56.3204789639</stddev><median>4.6</median></sell>' +
      '</type>' +
      '</marketstat></evec_api>';}

    // if the requested data was received correctly, process it
    if (canprocessresult) then begin
      gui.BringToFront;
      gui.Change('responses received from EVE-Central', 1);
      gui.Advance;
      application.ProcessMessages;
      if (gui.AbortedByUser) then exit;
      // Merge the strings to one XML result file
      // First, take the first result...
      XMLresult := TXMLDocument.Create(self);
      XMLresult.Active := true;
      XMLresult.LoadFromXML(textresult[0]);
      itemsnode := XMLresult.DocumentElement.ChildNodes[0]; // <marketstat> element
      // ...then append the items from the other results to it
      for i := 1 to textresult.Count -1 do begin
        tempXMLDoc := TXMLDocument.Create(self);
        tempXMLDoc.Active := true;
        tempXMLDoc.LoadFromXML(textresult[i]);
        tempitemsnode := tempXMLDoc.DocumentElement.ChildNodes[0]; // <marketstat> element
        for j := 0 to tempitemsnode.ChildNodes.Count -1 do begin
          node := tempitemsnode.ChildNodes[j];
          itemsnode.ChildNodes.Add(node.CloneNode(true));
        end;
        tempXMLDoc.Free;
      end;
      EveCentralRetrieverThread.Terminate;

      // Read all items in the result file
      resultroot := XMLresult.DocumentElement.ChildNodes[0]; // <marketstat> element
      gui.Change('item prices processed', resultroot.ChildNodes.Count);
      application.ProcessMessages;
      if (gui.AbortedByUser) then exit;
      for i := 0 to resultroot.ChildNodes.Count -1 do begin
        resultnode := resultroot.ChildNodes[i];
        // for each item in the result XML, look up its corresponding item in the XML file
        root := XMLDoc.DocumentElement.ChildNodes[0]; // <marketstat> element
        for j := 0 to root.ChildNodes.Count -1 do begin
          node := root.ChildNodes[j];
          if (node.Attributes['id'] = resultnode.Attributes['id']) then begin
            // item found. Remove the node, so its new values can be appended.
            root.ChildNodes.Remove(node);
            break;
          end;
        end;
        // Append item to the XML file and include timestamp.
        resultnode.AddChild('retrievedat');
        resultnode.ChildNodes['retrievedat'].Text := DateTimeToStr(Now);
        root.ChildNodes.Add(resultnode);
        // and of course also load the price
        tmpsetting := setting;
        while (AnsiContainsStr(tmpsetting, '\')) do begin
          resultnode := resultnode.ChildNodes[LeftStr(tmpsetting, Pos('\', tmpsetting) - 1)];
          tmpsetting := RightStr(tmpsetting, Length(tmpsetting) - Pos('\', tmpsetting));
        end;
        pricestring := resultnode.ChildNodes[tmpsetting].Text;
        pricestring := StringReplace(pricestring, '.', global_ds, [rfReplaceAll]);
        if (TryStrToFloat(pricestring, price)) then items[i].Price := price;
  //      else result.Error := 'Error retrieving price for item' + items[i].Name;
        gui.Advance;
        gui.BringToFront;
        application.ProcessMessages;
        if (gui.AbortedByUser) then exit;
      end;
      XMLDoc.SaveToFile('res\EVE-Central\AllItems.xml');
      retrieved := resultroot.ChildNodes.Count;
      XMLDoc.Free;
      XMLresult.Free;
    end;
  end;

  gui.Destroy;
  self.ModalResult := mrOk;

  // show message to the user
  if (cached = 1) then cachedstring := 'price'
  else cachedstring := 'prices';
  if (retrieved = 1) then retrievedstring := 'price'
  else retrievedstring := 'prices';
  message := 'Loaded ' + IntToStr(cached) + ' ' + cachedstring + ' from cache and successfully retrieved ' + IntToStr(retrieved) + ' ' + retrievedstring + ' from EVE-Central.';
  if (canprocessresult) then MessageDlg(message, mtInformation, [mbOk], 0);
end;

{function TfrmImportPrices.RetrievePrice(itemID: Integer; setting: String): SReturnValue;
var
  parameters: TStringList;
  XMLresult, pricestring: String;
  Fout: TextFile;
  http: TIdHTTP;
  retrieve, canprocessresult: Boolean;
  XMLDoc: TXMLDocument;
  node: IXMLNode;
  cachedtime, nowtime: TDateTime;
  price: Double;
begin
  result.Price := 0;
  result.Cached := false;
  result.Error := '';

  if (not DirectoryExists('res\EVE-Central')) then CreateDir('res\EVE-Central');
  retrieve := true;
  if (FileExists('res\EVE-Central\' + IntToStr(itemID) + '.xml')) then begin
    // if it exists, check if it is still cached
    XMLDoc := TXMLDocument.Create(self);
    XMLDoc.LoadFromFile('res\EVE-Central\' + IntToStr(itemID) + '.xml');
    if (TryStrToDateTime(XMLDoc.DocumentElement.ChildNodes['retrievedat'].Text, cachedtime)) then begin
      nowtime := Now;
      retrieve := (nowtime - cachedtime >= 1); // only allow retrieval once a day (per item)
      result.Cached := not retrieve;
    end;
    XMLDoc.Free;
  end;
  canprocessresult := true;
  if (retrieve) then begin
    // retrieve xml file from EVE-Central website
    parameters := TStringList.Create;
    parameters.Add('typeid=' + IntToStr(itemID));
    http := TIdHTTP.Create;
    try
      XMLresult := http.Post('http://eve-central.com/home/marketstat_xml.html', parameters);

      // write xml file to HD, and open it to append the current time (for caching purposes)
      AssignFile(Fout, 'res\EVE-Central\' + IntToStr(itemID) + '.xml');
      ReWrite(Fout);
      Write(Fout, XMLresult);
      CloseFile(Fout);
      XMLDoc := TXMLDocument.Create(self);
      XMLDoc.LoadFromFile('res\EVE-Central\' + IntToStr(itemID) + '.xml');
      XMLDoc.DocumentElement.AddChild('retrievedat');
      XMLDoc.DocumentElement.ChildNodes['retrievedat'].Text := DateTimeToStr(Now);
      XMLDoc.SaveToFile('res\EVE-Central\' + IntToStr(itemID) + '.xml');
      XMLDoc.Destroy;
    except
      on e: Exception do begin
        result.Error := e.Message;
        canprocessresult := false;
      end;
    end;
    parameters.Free;
    http.Free;
  end;
  if (canprocessresult) then begin
    // parse the file and return the price
    XMLDoc := TXMLDocument.Create(self);
    XMLDoc.LoadFromFile('res\EVE-Central\' + IntToStr(itemID) + '.xml');
    node := XMLDoc.DocumentElement;
    while (AnsiContainsStr(setting, '\')) do begin
      node := node.ChildNodes[LeftStr(setting, Pos('\', setting) - 1)];
      setting := RightStr(setting, Length(setting) - Pos('\', setting));
    end;
    pricestring := node.ChildNodes[setting].Text;
    pricestring := StringReplace(pricestring, '.', global_ds, [rfReplaceAll]);
    if (TryStrToFloat(pricestring, price)) then result.Price := price
    else result.Error := 'Error retrieving price from ' + IntToStr(itemID) + '.xml';
    XMLDoc.Destroy;
  end
  else result.Error := 'Unable to parse ' + IntToStr(itemID) + '.xml';
end;}

procedure TfrmImportPrices.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  inventionCalc.UserSettings.Settings.ImportPricesDefaultSettingIndex := rgSetting.ItemIndex;
end;

procedure TfrmImportPrices.btnRegionsAllClick(Sender: TObject);
var
  i: Integer;
  checkbox: TCheckBox;
begin
  for i := 0 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    checkbox.Checked := true;
  end;
end;

procedure TfrmImportPrices.btnRegionsEmpireClick(Sender: TObject);
var
  i: Integer;
  region: TRegion;
  checkbox: TCheckBox;
begin
  for i := 0 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    region := inventionCalc.Regions.GetRegionByID(StrToInt(RightStr(checkbox.Name, Length(checkbox.Name) - POS('_', checkbox.Name))));
    if ((region.Faction = FAC_Amarr) or
        (region.Faction = FAC_Caldari) or
        (region.Faction = FAC_Gallente) or
        (region.Faction = FAC_Minmatar) or
        (region.Faction = FAC_Khanid) or
        (region.Faction = FAC_Ammatar)) then
      checkbox.Checked := true
    else
      checkbox.Checked := false;
  end;
end;

procedure TfrmImportPrices.btnRegionsZeroClick(Sender: TObject);
var
  i: Integer;
  region: TRegion;
  checkbox: TCheckBox;
begin
  for i := 0 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    region := inventionCalc.Regions.GetRegionByID(StrToInt(RightStr(checkbox.Name, Length(checkbox.Name) - POS('_', checkbox.Name))));
    if ((region.Faction = FAC_Thukker) or
        (region.Faction = FAC_Angel) or
        (region.Faction = FAC_Guristas) or
        (region.Faction = FAC_Sansha) or
        (region.Faction = FAC_Syndicate) or
        (region.Faction = FAC_ORE) or
        (region.Faction = FAC_None)) then
      checkbox.Checked := true
    else
      checkbox.Checked := false;
  end;
end;

procedure TfrmImportPrices.btnRegionsNoneClick(Sender: TObject);
var
  i: Integer;
  checkbox: TCheckBox;
begin
  for i := 0 to gbRegions.ControlCount -1 do begin
    checkbox := TCheckBox(gbRegions.Controls[i]);
    checkbox.Checked := false;
  end;
end;

end.
